package com.hy.convenient.config;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.PrintWriter;
import java.io.StringWriter;

/**
 * 切面日志
 *
 * @author Buzheng
 */
@Aspect
@Component
public class RequestLogAspect {
    private final static Logger LOGGER = Logger.getLogger(RequestLogAspect.class);


    /**
     * 定义切入点
     */
    @Pointcut("execution(* com.hy.convenient.api.controller..*.*(..))")
    public void requestServer() {
    }

    /**
     * 环绕通知
     * 既可以在目标方法之前织入增强动作，也可以在执行目标方法之后织入增强动作；
     * 可以决定目标方法在什么时候执行，如何执行，甚至可以完全阻止目标目标方法的执行；
     * 可以改变执行目标方法的参数值，也可以改变执行目标方法之后的返回值； 当需要改变目标方法的返回值时，只能使用Around方法；
     */
    @Around("requestServer()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        //获取系统时间
        long start = System.currentTimeMillis();
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        //获取请求信息
        HttpServletRequest request = attributes.getRequest();
        Object result = proceedingJoinPoint.proceed();
        LOGGER.info(request.getServletPath());
        LOGGER.info(getRequestParamsByProceedingJoinPoint(proceedingJoinPoint));
        return result;
    }

    /**
     * 异常通知：目标方法抛出异常时执行
     */
    @AfterThrowing(pointcut = "requestServer()", throwing = "e")
    public void doAfterThrow(JoinPoint joinPoint, RuntimeException e) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        e.printStackTrace(pw);
        LOGGER.error(request.getServletPath());
        LOGGER.error(getRequestParamsByJoinPoint(joinPoint));
        LOGGER.error(sw.toString());
    }

    /**
     * 获取入参
     *
     * @param proceedingJoinPoint
     */
    private String getRequestParamsByProceedingJoinPoint(ProceedingJoinPoint proceedingJoinPoint) {
        //参数名
        String[] paramNames = ((MethodSignature) proceedingJoinPoint.getSignature()).getParameterNames();
        //参数值
        Object[] paramValues = proceedingJoinPoint.getArgs();
        //去除反斜杠
        return JSON.toJSONString(buildRequestParam(paramNames, paramValues).toString());
    }

    /**
     * 获取入参
     *
     * @param joinPoint
     */
    private String getRequestParamsByJoinPoint(JoinPoint joinPoint) {
        //参数名
        String[] paramNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
        //参数值
        Object[] paramValues = joinPoint.getArgs();
        return JSON.toJSONString(buildRequestParam(paramNames, paramValues).toString());
    }

    /**
     * 将参数名称以及参数值转成json字符串
     *
     * @param paramNames  参数名称
     * @param paramValues 参数值
     * @return cn.hutool.json.JSONObject
     * @author Buzheng
     **/
    private JSONObject buildRequestParam(String[] paramNames, Object[] paramValues) {
        JSONObject jsonObject = new JSONObject();
        for (int i = 0; i < paramNames.length; i++) {
            Object value = paramValues[i];
            //如果是文件对象
            if (value instanceof MultipartFile) {
                MultipartFile file = (MultipartFile) value;
                value = file.getOriginalFilename();  //获取文件名
            }
            jsonObject.put(paramNames[i], value != null ? value : null);
        }
        return jsonObject;
    }
}